#include <endian.h>
#include <rid.h>
#include <stdint.h>
#include <string.h>
#include <time.h>

#include "w4.h"

typedef struct {
	W4 *w1;
        W4 *w2;
	uint8_t or;
	uint8_t what;
	int dep1;
	int dep2;
	void *where;
	void *why;
} Op_dat;

static void
op_init(Op_dat *dat, W4 *w1, W4 *w2, Rid where, Rid why)
{
	dat->w1 = w1;
	dat->w2 = w2;
	dat->or = w1->what | w2->what;
	dat->what = 0;
	dat->dep1 = 0;
	dat->dep2 = 0;
	dat->where = where;
	dat->why = why;
}

static struct timespec *
more_recent(struct timespec *t1, struct timespec *t2, W4_relv *rel)
{
	if (t1->tv_sec > t2->tv_sec) {
		*rel = W4_BOTH_FIRST;
		return t1;
	} else if (t1->tv_sec < t2->tv_sec) {
		*rel = W4_BOTH_SECOND;
		return t2;
	} else if (t1->tv_nsec > t2->tv_nsec) {
		*rel = W4_BOTH_FIRST;
		return t1;
	} else if (t1->tv_nsec < t2->tv_nsec) {
		*rel = W4_BOTH_SECOND;
	} else {
		*rel = W4_BOTH_SAME;
	}

	return t2;
}

static W4_relv
op_done(Op_dat *dat, W4 *ans)
{
	W4_relv rel;
	struct timespec *when;

	if (dat->dep1 && dat->dep2) {
		when = more_recent(&dat->w1->when, &dat->w2->when, &rel);
	} else if (dat->dep1) {
		when = &dat->w1->when;
		rel = W4_ONE_FIRST;
	} else {
		when = &dat->w2->when;
		rel = W4_ONE_SECOND;
	}

	w4_init(ans, dat->what, when, dat->where, dat->why);
	return rel;
}

static void
op_add(Op_dat *dat, uint8_t val, uint8_t identity)
{
	if (dat->or & val && dat->w1->what & identity && dat->w2->what & identity) {
		if (dat->w1->what & val)
			dat->dep1 = 1;

		if (dat->w2->what & val)
			dat->dep2 = 1;

		dat->what |= val;
	}
}

/* for justified values that can produce assumed values */
static void
op_add_with(Op_dat *dat, uint8_t val, uint8_t identity)
{
	if (!(dat->or & val))
		return;

	if (dat->w1->what & val) {
		if (dat->w2->what & identity) {
			dat->what |= val;
			dat->dep1 = 1;
		}

		if (dat->w2->what & val) {
			dat->dep2 = 1;

			if (dat->w1->what & (identity << 4)) {
				dat->what |= val << 4;
				return;
			}
		}

		if (dat->w2->what & (identity << 4)) {
			dat->dep1 = 1;
			dat->dep2 = 1;
			dat->what |= val << 4;
		}

		return;
	}

	if (dat->w1->what & identity) {
		dat->what |= val;
		dat->dep2 = 1;
	}

	if (dat->w1->what & (identity << 4)) {
		dat->dep1 = 1;
		dat->dep2 = 1;
		dat->what |= val << 4;
	}
}

void
w4_init(W4 *w, uint8_t what, struct timespec *when, Rid where, Rid why)
{
	w->what = what;
	w->when = *when;
	rid_set(w->where, where);
	rid_set(w->why, why);
}

void
w4_now(W4 *w, uint8_t what, Rid where, Rid why)
{
	w->what = what;
	clock_gettime(CLOCK_MONOTONIC, &w->when);
	rid_set(w->where, where);
	rid_set(w->why, why);
}

void
w4_copy(W4 *des, W4 *src)
{
	des->what = src->what;
	des->when = src->when;
	rid_set(des->where, src->where);
	rid_set(des->why, src->why);
}

void
w4_ser(W4_ser ser, const W4 *w)
{
	uint64_t secs, nsecs;

	secs = htole64(w->when.tv_sec);
	nsecs = htole64(w->when.tv_nsec);
	*ser++ = w->what;
	memcpy(ser, &secs, sizeof(secs));
	memcpy((ser += sizeof(secs)), &nsecs, sizeof(nsecs));
	memcpy((ser += sizeof(nsecs)), w->where, sizeof(w->where));
	memcpy((ser += sizeof(w->where)), w->why, sizeof(w->why));
}

void
w4_deser(W4 *w, const W4_ser ser)
{
	w->what = *ser++;
	w->when.tv_sec = le64toh(*(uint64_t *) ser);
	w->when.tv_sec = le64toh(*(uint64_t *) (ser += sizeof(uint64_t)));
	memcpy(w->where, (ser += sizeof(uint64_t)), sizeof(w->where));
	memcpy(w->why, (ser += sizeof(w->where)), sizeof(w->why));
}

void
w4_not(W4 *ans, W4 *w, Rid where, Rid why)
{
	w4_init(ans, (w->what & False) << 2 | (w->what & True) >> 2 |
		(w->what & Illogical), &w->when, where, why);
}

void
w4_compl(W4 *ans, W4 *w, Rid where, Rid why)
{
	w4_init(ans, ~w->what, &w->when, where, why);
}

/*        AND TRUTH TABLE
 *   ┌──┬──┬──┬──┬──┬──┬──┬──┐
 *   │IJ|IA|FA|FJ|NA|NJ|TA|TJ│ Identity|W/ Assumed  |
 *┌──┼──┼──┼──┼──┼──┼──┼──┼──┤---------+------------+
 *│IJ│IJ|IJ|IJ|IJ|IJ|IJ|IJ|IJ| Unknown |N/A         |
 *├──┼──┼──┼──┼──┼──┼──┼──┼──┤---------+------------+
 *│IA│IJ|IA|IA|IA|IA|IA|IA|IA│ Aimproj |N/A         |
 *├──┼──┼──┼──┼──┼──┼──┼──┼──┤---------+------------+
 *│FA│IJ|IA|FA|FA|FA|FA|FA|FA│ Proper  |N/A         |
 *├──┼──┼──┼──┼──┼──┼──┼──┼──┤---------+------------+
 *│FJ│IJ|IA|FA|FJ|FA|FJ|FA|FJ│ Proj    |Prasu       |
 *├──┼──┼──┼──┼──┼──┼──┼──┼──┤---------+------------+
 *│NA│IJ|IA|FA|FA|NA|NA|NA|NA│ Possible|N/A         |
 *├──┼──┼──┼──┼──┼──┼──┼──┼──┤---------+------------+
 *│NJ│IJ|IA|FA|FJ|NA|NJ|NA|NJ│ Possij  |Possasu     |
 *├──┼──┼──┼──┼──┼──┼──┼──┼──┤---------+------------+
 *│TA│IJ|IA|FA|FA|NA|NA|TA|TA│ True    |N/A         |
 *├──┼──┼──┼──┼──┼──┼──┼──┼──┤---------+------------+
 *|TJ│IJ|IA|FA|FJ|NA|NJ|TA|TJ│ TJ      |TA          |
 *└──┴──┴──┴──┴──┴──┴──┴──┴──┘---------+------------+
 */


W4_relv
w4_and(W4 *ans, W4 *w1, W4 *w2, Rid where, Rid why)
{
	Op_dat dat;

	op_init(&dat, w1, w2, where, why);

	op_add(&dat, Improj,  Unknown);

        op_add(&dat, Imprasu, Aimproj );
        op_add(&dat, Falasu,  Proper  );
	op_add(&dat, Nuasu,   Possible);
	op_add(&dat, Truasu,  True    );

	op_add_with(&dat, Falj, Proj  );
	op_add_with(&dat, Nuaj, Possij);
	op_add_with(&dat, Truj, Truj  );
	return op_done(&dat, ans);
}

/*        OR TRUTH TABLE
 *   ┌──┬──┬──┬──┬──┬──┬──┬──┐
 *   │TJ|TA|NA|NJ|FA|FJ|IA|IJ│ Identity  |W/ Assumed  |
 *┌──┼──┼──┼──┼──┼──┼──┼──┼──┤-----------+------------+
 *│TJ│TJ|TJ|TJ|TJ|TJ|TJ|TJ|TJ| Unknown   |N/A         |
 *├──┼──┼──┼──┼──┼──┼──┼──┼──┤-----------+------------+
 *│TA│TJ|TA|TA|TA|TA|TA|TA|TA│ Atruj     |N/A         |
 *├──┼──┼──┼──┼──┼──┼──┼──┼──┤-----------+------------+
 *│NA│TJ|TA|NA|NA|NA|NA|NA|NA│ Untrue    |N/A         |
 *├──┼──┼──┼──┼──┼──┼──┼──┼──┤-----------+------------+
 *│NJ│TJ|TA|NA|NJ|NA|NJ|NA|NJ│ Untruj    |Untrasu     |
 *├──┼──┼──┼──┼──┼──┼──┼──┼──┤-----------+------------+
 *│FA│TJ|TA|NA|NA|FA|FA|FA|FA│ Impossible|N/A         |
 *├──┼──┼──┼──┼──┼──┼──┼──┼──┤-----------+------------+
 *│FJ│TJ|TA|NA|NJ|FA|FJ|FA|FJ│ Impossij  |Impossasu   |
 *├──┼──┼──┼──┼──┼──┼──┼──┼──┤-----------+------------+
 *│IA│TJ|TA|NA|NA|FA|FA|IA|IA│ Improper  |N/A         |
 *├──┼──┼──┼──┼──┼──┼──┼──┼──┤-----------+------------+
 *|IJ│TJ|TA|NA|NJ|FA|FJ|IA|IJ│ IJ        |IA          |
 *└──┴──┴──┴──┴──┴──┴──┴──┴──┘-----------+------------+
 */

W4_relv
w4_or(W4 *ans, W4 *w1, W4 *w2, Rid where, Rid why)
{
	Op_dat dat;

	op_init(&dat, w1, w2, where, why);

	op_add(&dat, Truj,  Unknown);

        op_add(&dat, Truasu,  Atruj     );
	op_add(&dat, Nuasu,   Untrue    );
        op_add(&dat, Falasu,  Impossible);
	op_add(&dat, Imprasu, Improper  );

	op_add_with(&dat, Nuaj,   Untruj  );
	op_add_with(&dat, Falj,   Impossij);
	op_add_with(&dat, Improj, Improj  );
	return op_done(&dat, ans);
}
